package ru.dc;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import ru.dc.object.*;
import ru.dc.object.events.download.DownloadQueueEvent;
import ru.dc.object.events.hub.ConnectionToClientRequested;
import ru.dc.object.events.hub.HubEvent;

import java.util.ArrayList;
import java.util.List;
import java.util.Vector;

/**
 * <pre>
 * User: Penkov Vladimir
 * Date: 01.11.2010
 * Time: 16:13:09
 * </pre>
 */
public class DownloadQueue extends Thread implements HubEventListener {

    private Vector<DownloadItem> downloadQueue = new Vector<DownloadItem>();
    private Vector<DownloadItem> activeDownloadQueue = new Vector<DownloadItem>();
    private Vector<DownloadSource> activeDownloadSources = new Vector<DownloadSource>();
    private boolean stopRequested = false;
    private Log logger = LogFactory.getLog(getClass());
    private List<DownloadListener> listeners = new ArrayList<DownloadListener>();

    private final Object lock = new Object();

    public void enqueueItem(DownloadItem item) {
        synchronized (lock) {
            downloadQueue.add(item);
            lock.notifyAll();
        }
        fireDownloadItemAdded(item);
        logger.info("Added "+item);
    }


    @Override
    public void run() {
        logger.info("Started monitoring downloads");

        Application.getApplication().getHubEventsTranslator().addEventListener(this);

        while (!stopRequested) {
            synchronized (lock) {
                if (downloadQueue.isEmpty()) {
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        logger.error("Can't wait for download item: "+e.getMessage(), e);
                    }
                }
            }
            List<DownloadItem> items = new ArrayList<DownloadItem>(downloadQueue);
            if (!items.isEmpty()) {
                for (DownloadItem item : items) {
                    startDownload(item);
                }
            }
        }
        Application.getApplication().getExternalEventsTranslator().removeEventListener(this);
    }

    private void startDownload(DownloadItem item) {
        if (activeDownloadQueue.contains(item) || activeDownloadSources.contains(item.getSource())) {
            return;
        }
        if (item.getSource().getHub().isActive()) {
            Application.getApplication().getConnectionManager().askForActiveConnection(item.getSource());
        }
        else {
            Application.getApplication().getConnectionManager().askForPassiveConnection(item.getSource());
        }

        //todo здесь возможно нужно вести список загрузок, для которых был отправлен запрос на соединение
        //чтобы не кидаться запросами на каждый запуск потока DownloadQueue
        final DownloadQueueEvent e = new DownloadQueueEvent();
        e.setItem(item);
        e.setOperation(DownloadQueueEvent.ITEM_CONNECTING);
        fireDownloadEvent(e);
    }

    public void requestStop() {
        stopRequested = true;
    }

    public void onEvent(HubEvent e) {
        if (e instanceof ConnectionToClientRequested) {
            //пришел запрос на подключение от другого клиента. Добавляем новое соединение.
            //todo нужно вынести этот код в другой класс, так как конкретно к загрузке он не имеет никакого отношения
            ConnectionToClientRequested req = (ConnectionToClientRequested) e;
            final ServerInfo serverInfo = new ServerInfo();
            serverInfo.setAddress(req.getAddress());
            serverInfo.setPort(req.getPort());
            logger.info("Attemping to connect to "+serverInfo);
            Application.getApplication().getConnectionManager().addClientConnection(serverInfo, e.getServerInfo());
        }
    }


    /**
     * Возвращает очередной элемент для загрузки для указанного пользователя.
     * @param source источник загружки
     * @return элемент загрузки
     */
    public DownloadItem findNextDownload(DownloadSource source) {
        for (DownloadItem item : downloadQueue) {
            if (!activeDownloadQueue.contains(item) && item.getSource().equals(source)) {
                return item;
            }
        }
        return null;
    }


    /**
     * Помечает загрузку завершенной. Это означает, что загрузка будет убрана из списка загрузок (с генерацией
     * соответствующего события) и из списка активных загрузок. Источник загрузки из списка активных источников
     * удален не будет.
     * @param item загрузка
     */
    public void markDownloaded(DownloadItem item) {
        downloadQueue.remove(item);
        activeDownloadQueue.remove(item);
        fireDownloadItemFinished(item);
        fireDownloadItemRemoved(item);
    }

    /**
     * Помечает загрузку активной. Также активным помечается источник загрузки.
     * Это означает, что при следующем запуске очереди загрузок для данной загрузки
     * и всех загрузок от данного источника не будет отправляться запрос на соединение.
     * @param item загрузка
     */
    public void markActive(DownloadItem item) {
        activeDownloadQueue.add(item);
        activeDownloadSources.add(item.getSource());
    }

    /**
     * Убирает источник загрузок из списка активных источников загрузок.
     * Это означает, что при следующем запуске очереди загрузок, если в списке загрузок будет
     * находится элемент с этим источником, то источнику будет отправлен запрос на соединение.
     * @param source источник
     */
    public void releaseDownloadSource(DownloadSource source) {
        activeDownloadSources.remove(source);
    }

    public void markInactive(DownloadItem item) {
        activeDownloadQueue.remove(item);
        activeDownloadSources.remove(item.getSource());
    }

    public Vector<DownloadItem> getDownloadQueue() {
        return downloadQueue;
    }

    public Vector<DownloadItem> getActiveDownloadQueue() {
        return activeDownloadQueue;
    }

    public void addDownloadListener(DownloadListener listener) {
        listeners.add(listener);
    }

    public void removeDownloadListener(DownloadListener listener) {
        listeners.remove(listener);
    }

    public void fireDownloadItemAdded(DownloadItem item) {
        DownloadQueueEvent e = new DownloadQueueEvent();
        e.setItem(item);
        e.setOperation(DownloadQueueEvent.ITEM_ADDED);
        fireDownloadEvent(e);
    }

    public void fireDownloadItemRemoved(DownloadItem item) {
        DownloadQueueEvent e = new DownloadQueueEvent();
        e.setItem(item);
        e.setOperation(DownloadQueueEvent.ITEM_REMOVED);
        fireDownloadEvent(e);
    }

    public void fireDownloadItemFinished(DownloadItem item) {
        DownloadQueueEvent e = new DownloadQueueEvent();
        e.setItem(item);
        e.setOperation(DownloadQueueEvent.ITEM_DOWNLOADED);
        fireDownloadEvent(e);
    }

    public void fireDownloadEvent(DownloadQueueEvent e) {
        for (DownloadListener l : listeners) {
            l.onDownloadEvent(e);
        }
    }
}
